package io.github.jsp.scheduler;

import io.github.jsp.model.JobInfo;
import io.github.jsp.wheel.HashedWheelTimer;
import io.github.jsp.wheel.Timeout;
import io.github.jsp.wheel.TimerTask;
import org.quartz.CronExpression;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.text.ParseException;
import java.util.Date;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

@Component
public class TimeWheelScheduler {
    
    private static final Logger logger = LoggerFactory.getLogger(TimeWheelScheduler.class);
    
    private HashedWheelTimer timer;
    
    private final ConcurrentHashMap<Integer, Timeout> jobTimeouts = new ConcurrentHashMap<>();
    
    @Autowired
    private JobTriggerService jobTriggerService;
    
    @PostConstruct
    public void init() {
        timer = new HashedWheelTimer(1, TimeUnit.SECONDS, 512);
        logger.info("TimeWheelScheduler initialized");
    }
    
    @PreDestroy
    public void destroy() {
        if (timer != null) {
            timer.stop();
            logger.info("TimeWheelScheduler destroyed");
        }
    }
    
    public void scheduleJob(JobInfo jobInfo) {
        if (jobInfo == null || jobInfo.getId() == null) {
            logger.warn("Invalid job info for scheduling");
            return;
        }
        
        unscheduleJob(jobInfo.getId());
        
        try {
            CronExpression cronExpr = new CronExpression(jobInfo.getJobCron());
            Date nextFireTime = cronExpr.getNextValidTimeAfter(new Date());
            
            if (nextFireTime == null) {
                logger.warn("No valid next fire time for job: {}", jobInfo.getId());
                return;
            }
            
            long delay = nextFireTime.getTime() - System.currentTimeMillis();
            if (delay <= 0) {
                delay = 1000;
            }
            
            Timeout timeout = timer.newTimeout(new JobTimerTask(jobInfo), delay, TimeUnit.MILLISECONDS);
            jobTimeouts.put(jobInfo.getId(), timeout);
            
            jobInfo.setTriggerNextTime(nextFireTime.getTime());
            
            logger.debug("Scheduled job: {} to fire at: {}, delay: {}ms", 
                jobInfo.getId(), nextFireTime, delay);
            
        } catch (ParseException e) {
            logger.error("Invalid cron expression for job: {}, cron: {}", 
                jobInfo.getId(), jobInfo.getJobCron(), e);
        }
    }
    
    public void unscheduleJob(Integer jobId) {
        Timeout timeout = jobTimeouts.remove(jobId);
        if (timeout != null) {
            timeout.cancel();
            logger.debug("Unscheduled job: {}", jobId);
        }
    }
    
    public boolean isJobScheduled(Integer jobId) {
        Timeout timeout = jobTimeouts.get(jobId);
        return timeout != null && !timeout.isCancelled();
    }
    
    private class JobTimerTask implements TimerTask {
        private final JobInfo jobInfo;
        
        public JobTimerTask(JobInfo jobInfo) {
            this.jobInfo = jobInfo;
        }
        
        @Override
        public void run(Timeout timeout) throws Exception {
            try {
                if (timeout.isCancelled()) {
                    return;
                }
                
                jobTimeouts.remove(jobInfo.getId());
                
                jobTriggerService.trigger(jobInfo);
                
                if (jobInfo.getTriggerStatus() == 1) {
                    scheduleJob(jobInfo);
                }
                
            } catch (Exception e) {
                logger.error("Error executing job timer task for job: {}", jobInfo.getId(), e);
            }
        }
    }
}